cmake_minimum_required(VERSION 3.10)
project(DebugUtils LANGUAGES C Fortran)

# Add CUDA language support
option(ENABLE_CUDA "Enable CUDA support" OFF)
if(ENABLE_CUDA)
  # CMake 3.10 requires this method for CUDA
  enable_language(CUDA)
  
  # Set CUDA architecture if not defined
  if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
    set(CMAKE_CUDA_ARCHITECTURES "89")
    message(STATUS "CUDA architecture not specified, defaulting to sm_89")
  endif()
  
  # Define CUDA_ENABLED for conditional compilation
  add_compile_definitions(CUDA_ENABLED)
endif()

# Option to enable/disable tests
option(BUILD_TESTING "Build tests" OFF)

# Enable testing if requested
if(BUILD_TESTING)
  include(CTest)
  
  # Find MPI package for tests
  find_package(MPI)
  
  # Disable tests if MPI not found and CUDA is not enabled
  if(NOT MPI_FOUND AND NOT ENABLE_CUDA)
    message(WARNING "MPI not found and CUDA not enabled - disabling tests. To build without this warning, use -DBUILD_TESTING=OFF")
    set(BUILD_TESTING OFF)
  endif()
endif()

# Set position independent code for all targets
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

# Set installation directories
set(CMAKE_INSTALL_BINDIR bin)
set(CMAKE_INSTALL_LIBDIR lib)
set(CMAKE_INSTALL_INCLUDEDIR include)

# C Library (shared)
add_library(cdebug_utils SHARED compare-diff/debug_print.c)
set_target_properties(cdebug_utils PROPERTIES
    PUBLIC_HEADER compare-diff/debug_utils.h
    OUTPUT_NAME cdebug_utils)
target_include_directories(cdebug_utils PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/compare-diff>
    $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
target_link_libraries(cdebug_utils PRIVATE m)  # Link with math library for sqrt

# C Library (static)
add_library(cdebug_utils_static STATIC compare-diff/debug_print.c)
set_target_properties(cdebug_utils_static PROPERTIES
    PUBLIC_HEADER compare-diff/debug_utils.h
    OUTPUT_NAME cdebug_utils)
target_include_directories(cdebug_utils_static PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/compare-diff>
    $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
target_link_libraries(cdebug_utils_static PRIVATE m)  # Link with math library for sqrt

# CUDA Library if enabled
if(ENABLE_CUDA)
  # Find CUDA and cuBLAS
  find_package(CUDAToolkit REQUIRED)
  
  # CUDA Library (shared)
  add_library(cudadebug_utils SHARED compare-diff/debug_print.cu)
  set_target_properties(cudadebug_utils PROPERTIES
      PUBLIC_HEADER compare-diff/debug_utils.h
      OUTPUT_NAME cudadebug_utils)
  target_include_directories(cudadebug_utils PUBLIC
      $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/compare-diff>
      $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
  target_link_libraries(cudadebug_utils PRIVATE CUDA::cudart CUDA::cublas)
  
  # CUDA Library (static)
  add_library(cudadebug_utils_static STATIC compare-diff/debug_print.cu)
  set_target_properties(cudadebug_utils_static PROPERTIES
      PUBLIC_HEADER compare-diff/debug_utils.h
      OUTPUT_NAME cudadebug_utils)
  target_include_directories(cudadebug_utils_static PUBLIC
      $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/compare-diff>
      $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
  target_link_libraries(cudadebug_utils_static PRIVATE CUDA::cudart CUDA::cublas)
endif()

# Fortran library (static)
add_library(fdebug_utils_static STATIC compare-diff/debug_utils.F90)
set_target_properties(fdebug_utils_static PROPERTIES
    OUTPUT_NAME fdebug_utils
    Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/modules)

# Try to build shared Fortran library, but continue if it fails
try_compile(FORTRAN_SHARED_WORKS
  ${CMAKE_BINARY_DIR}/fortran_shared_test
  ${CMAKE_CURRENT_SOURCE_DIR}/compare-diff/debug_utils.F90
  OUTPUT_VARIABLE FORTRAN_SHARED_OUTPUT
)

if(FORTRAN_SHARED_WORKS)
  # Fortran library (shared)
  add_library(fdebug_utils SHARED compare-diff/debug_utils.F90)
  set_target_properties(fdebug_utils PROPERTIES
      OUTPUT_NAME fdebug_utils
      Fortran_MODULE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/modules)
  install(TARGETS fdebug_utils LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
  message(STATUS "Building shared Fortran library")
else()
  message(STATUS "Skipping shared Fortran library (not supported on this platform)")
  message(STATUS "Only static Fortran library will be available")
endif()

# Test executables
if(BUILD_TESTING)
  # MPI-based tests if MPI is found
  if(MPI_FOUND)
    # C test executable
    add_executable(test_c test/test_c.c)
    target_include_directories(test_c PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/compare-diff)
    target_link_libraries(test_c PRIVATE cdebug_utils MPI::MPI_C m)
    target_compile_definitions(test_c PRIVATE USE_MPI)
    
    # Fortran test executable
    add_executable(test_f test/test_f.f90)
    target_include_directories(test_f PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/modules)
    target_link_libraries(test_f PRIVATE fdebug_utils_static MPI::MPI_Fortran)
  endif()
  
  # CUDA test executable if enabled (no MPI dependency)
  if(ENABLE_CUDA)
    add_executable(test_cuda test/test_cuda.cu)
    target_include_directories(test_cuda PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/compare-diff)
    # Link with both C and CUDA debug libraries
    target_link_libraries(test_cuda PRIVATE cdebug_utils cudadebug_utils CUDA::cudart CUDA::cublas)
    add_test(NAME CUDA_Test COMMAND $<TARGET_FILE:test_cuda>)
    
    # Double-precision CUDA test
    add_executable(test_cuda_d test/test_cuda_d.cu)
    target_include_directories(test_cuda_d PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/compare-diff)
    # Link with both C and CUDA debug libraries
    target_link_libraries(test_cuda_d PRIVATE cdebug_utils cudadebug_utils CUDA::cudart CUDA::cublas)
    add_test(NAME CUDA_Double_Test COMMAND $<TARGET_FILE:test_cuda_d>)
  endif()
  
  # Create separate test scripts and tests for better isolation
  if(MPI_FOUND OR ENABLE_CUDA)
    # Create simple comparison script first
    configure_file(
      ${CMAKE_CURRENT_SOURCE_DIR}/compare-diff/sh/compare_norm_simple.sh
      ${CMAKE_BINARY_DIR}/compare_norm_simple.sh
      COPYONLY
    )
    execute_process(COMMAND chmod +x ${CMAKE_BINARY_DIR}/compare_norm_simple.sh)

    # Add individual tests instead of a combined script
    if(MPI_FOUND)
      add_test(NAME C_Test COMMAND ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} 2 $<TARGET_FILE:test_c>)
      add_test(NAME Fortran_Test COMMAND ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} 2 $<TARGET_FILE:test_f>)
    endif()
  endif()
endif()

# Installation
install(TARGETS cdebug_utils cdebug_utils_static
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

# Install CUDA libraries if enabled
if(ENABLE_CUDA)
  install(TARGETS cudadebug_utils cudadebug_utils_static
      LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
      ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
      PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
endif()

install(TARGETS fdebug_utils_static 
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})

install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/modules/
    DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

# Install the comparison scripts
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/compare_norm_simple.sh")
    # If the script exists in the root directory, use that
    set(COMPARE_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/compare_norm_simple.sh")
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/compare-diff/sh/compare_norm_simple.sh")
    # Otherwise, use the one in compare-diff/sh
    set(COMPARE_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/compare-diff/sh/compare_norm_simple.sh")
else()
    message(WARNING "Could not find compare_norm_simple.sh script")
endif()

install(FILES 
    compare-diff/compare_norm.py
    ${COMPARE_SCRIPT}
    DESTINATION ${CMAKE_INSTALL_BINDIR}
    PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE 
                GROUP_READ GROUP_EXECUTE 
                WORLD_READ WORLD_EXECUTE) 